/**
 * Copyright ©2016-2020 northkingbpo Corporation, All Rights Reserved
 * http://www.northkingbpo.net/
 */
package profile.hujia.algorithm.iteration;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import profile.hujia.algorithm.simulate.InitialCondition;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * class info
 *
 * @author hujia
 * @date 2019-07-12 16:29
 */
@Data
@Slf4j
public class Optimization {
    private InitialCondition initialCondition;
    //Pi值
    private HashMap<Integer, Integer> keyDiff;
    private HashMap<Integer, Integer> peopleQueueCapacity;
    //分配结果
    private HashMap<Integer, Integer> allocateResult;
    //无法分配
    private List<Integer> taskCannotAllot;
    //任务种类所需分配类别的范围
    private List<Integer> range;

    public Optimization(InitialCondition initialCondition) {
        this.initialCondition = initialCondition;
        peopleQueueCapacity = new HashMap<>();
        keyDiff = new HashMap<>();
        allocateResult = new HashMap<>();
        taskCannotAllot = new ArrayList<>();
        range = new ArrayList<>();
        for (int i = 0; i < initialCondition.getKeyNumber(); ++i) {
            range.add(i);
        }
    }

    public void run() {
        calculateDiff();
        while (true) {
            clearKeyValueEqualZero();
            if (range.isEmpty()) {
                break;
            }
            if (allotKeyDiffLessEqualThanZero()) {
                continue;
            }
            allotMinimum();
        }
    }

    /**
     * 当资源复杂都大于任务数时，找资源队负载最小的进行分配
     *
     * @param
     */
    private void allotMinimum() {
        //在可选范围内找到最小的任务种类
        int key = findMinimum();
        int people = getPeopleFromQueue(key);
        int task = taskOutQueue(key);
        allotTaskToPeople(task, people, key);
    }

    /**
     * 找出资源总负载能力与负载中最小的值。
     *
     * @return 找出最小值
     */
    private Integer findMinimum() {
        int min = 100;
        int key = -1;
        for (Integer tmpKey : range) {
            int tmpValue = keyDiff.get(tmpKey);
            if (tmpValue < min) {
                min = tmpValue;
                key = tmpKey;
            }
        }
        return key;
    }

    /**
     * 分配资源与队列之差小于等于0的任务种类
     *
     * @return true 任务分配完毕 false 没有分配
     */
    private boolean allotKeyDiffLessEqualThanZero() {
        boolean flag = false;
        for (Integer key : range) {
            if (keyDiff.get(key) <= 0) {
                //分配任务
                while (allotTaskToPeopleUntilPeopleOut(key)) ;
                flag = true;
            }
        }
        return flag;
    }

    /**
     * 找到任务队列为空，或者资源队列为空。
     *
     * @param
     * @return
     */
    private void clearKeyValueEqualZero() {
        List<Integer> clearList = new ArrayList<>();
        for (int i = 0; i < range.size(); ++i) {
            Integer key = range.get(i);
            //findPeopleQueueZero
            List<Integer> peopleQueue = initialCondition.getPeopleQueue().get(key);
            List<Integer> taskQueue = initialCondition.getTaskQueue().get(key);
            if (peopleQueue.isEmpty() && !taskQueue.isEmpty()) {
                //确定清除
                clearList.add(key);
                //任务全部无法分配
                listTaskCannotAllot(taskQueue);
            } else if (peopleQueue.isEmpty() && taskQueue.isEmpty()) {
                clearList.add(key);
            } else if (!peopleQueue.isEmpty() && taskQueue.isEmpty()) {
                clearList.add(key);
                //去掉资源能力
                deletePeopleListAbility(key, peopleQueue);
            }
        }
        for (Integer key : clearList) {
            range.remove(key);
        }
    }

    /**
     * function description
     *
     * @param key         任务种类(具体能力)
     * @param peopleQueue 资源队列
     */
    private void deletePeopleListAbility(int key, List<Integer> peopleQueue) {
        for (Integer people : peopleQueue) {
            deleteAbility(key, people);
        }
    }

    /**
     * 任务无法分配
     *
     * @param taskList 无法分配的任务列表
     */
    private void listTaskCannotAllot(List<Integer> taskList) {
        for (Integer task : taskList) {
            allotTaskToNone(task);
        }
    }


    /**
     * 资源队列与任务队列之差
     */
    private void calculateDiff() {
        //资源队列的总负载能力
        calculatePeopleQueueTotalCapacity();
        calculateTaskAndTotalCapacityDiff();
    }

    /**
     * 计算资源队列总负载能力
     */
    private void calculatePeopleQueueTotalCapacity() {
        for (int i = 0; i < initialCondition.getKeyNumber(); ++i) {
            List<Integer> peopleQueue = initialCondition.getPeopleQueue().get(i);
            int totalCapacity = 0;
            for (int j = 0; j < peopleQueue.size(); ++j) {
                int people = peopleQueue.get(j);
                int capacity = initialCondition.getPeopleCapacity().get(people);
                totalCapacity += capacity;
            }
            peopleQueueCapacity.put(i, totalCapacity);
        }
    }

    /**
     * 计算任务队列长度，并计算资源总负载与任务队列长度之差
     */
    private void calculateTaskAndTotalCapacityDiff() {
        for (int i = 0; i < initialCondition.getKeyNumber(); ++i) {
            int peopleTotalCapacity = peopleQueueCapacity.get(i);
            int taskLength = initialCondition.getTaskQueue().get(i).size();
            int diff = peopleTotalCapacity - taskLength;
            keyDiff.put(i, diff);
        }
    }


    /**
     * 任务分配至资源出队,两种情况：1，任务数大于5，任务数小于5。
     * 当任务完毕时候，删除资源对应能力，更新资源队列。当资源出队，就从所有种类队列中删除。
     *
     * @param key 队列种类
     * @return true 资源出队 false 任务消耗完毕 或者 没有资源。
     */
    public boolean allotTaskToPeopleUntilPeopleOut(int key) {
        int people = getPeopleFromQueue(key);
        if (people == -1) {
            return false;
        }
        boolean isPeopleOutQueue = false;
        while (!isPeopleOutQueue) {
            int task = taskOutQueue(key);
            //任务分配消耗完毕
            if (task == -1) {
                //已分配
                allocateResult.put(key, 1);
                deleteAbility(key, people);
                return false;
            }
            isPeopleOutQueue = allotTaskToPeople(task, people, key);
        }
        return true;
    }

    /**
     * 删除某个资源的某种能力
     *
     * @param key    队列种类
     * @param people 类别
     */
    private void deleteAbility(Integer key, int people) {
        List<Integer> keyList=initialCondition.getPeopleAbility().get(people);
        if (!keyList.remove(key)) {
            log.info("能力删除失败");
        }
        //获得该资源排序分数
        int sortValue = keyList.size();
        for (Integer tmpKey : keyList) {
            //获取该类别的资源排序队列
            List<Integer> peopleQueue = initialCondition.getPeopleQueue().get(tmpKey);
            //找到该资源在排序队列的位置
            Integer pos = peopleQueue.indexOf(people);
            //与排它前面的资源挨个比较
            for (int i = pos - 1; i >= 0; --i) {
                int tmpPeople = peopleQueue.get(i);
                int tmpSortValue = initialCondition.getPeopleAbility().get(tmpPeople).size();
                if (sortValue < tmpSortValue) {
                    swap(pos, i, peopleQueue);
                    pos = i;
                } else {
                    break;
                }
            }
        }
    }

    private void swap(int posA, int posB, List<Integer> array) {
        int tmp = array.get(posA);
        array.set(posA, array.get(posB));
        array.set(posB, tmp);
    }

    /**
     * 从队列里获取资源
     *
     * @param key 队列种类
     * @return
     */
    private int getPeopleFromQueue(int key) {
        List<Integer> peopleQueue = initialCondition.getPeopleQueue().get(key);
        if (peopleQueue.isEmpty()) {
            return -1;
        } else {
            return peopleQueue.get(0);
        }
    }


    /**
     * 任务出队,先进先出,数组0代表后进的任务，优先级教低。
     *
     * @param key 队列种类
     * @return 任务 -1表示没有任务了
     */
    private int taskOutQueue(int key) {
        List<Integer> taskQueue = initialCondition.getTaskQueue().get(key);
        if (taskQueue.isEmpty()) {
            return -1;
        } else {
            return taskQueue.remove(0);
        }
    }

    /**
     * 将任务分配给资源,并且资源空闲度为0的时候，从所有队列出队。
     *
     * @param task   任务
     * @param people 资源
     * @param key    任务种类
     * @return true表示人出队，false表示人没有出队。
     */
    private boolean allotTaskToPeople(int task, int people, int key) {
        log.info(String.format("task %d allot to people %d.", task, people));
        //record
        allocateResult.put(task, people);
        //capacity decrement
        int result = peopleCapacityDecrement(people);
        //diff update
        diffUpdate(key, people);
        if (result == 0) {
            removePeopleAllQueue(people);
            return true;
        }
        return false;
    }

    /**
     * 更新资源总负载与任务队列长度之差，除key之外，其余资源所涉及的都需要更新
     *
     * @param key 任务种类
     */
    private void diffUpdate(int key, int people) {
        //找出资源的所有能力
        List<Integer> ability = initialCondition.getPeopleAbility().get(people);
        for (Integer tmpKey : ability) {
            if (tmpKey == key) {
                continue;
            }
            int tmpValue = keyDiff.get(tmpKey) - 1;
            keyDiff.put(tmpKey, tmpValue);
        }
    }


    /**
     * 任务无法分配
     *
     * @param
     * @return
     */
    private void allotTaskToNone(int task) {
        log.info(String.format("task %d can't allot.", task));
        taskCannotAllot.add(task);
    }

    /**
     * 资源从所有队列中出队
     *
     * @param people 资源
     */
    private void removePeopleAllQueue(int people) {
        //find peopleAbility
        List<Integer> abilityQueue = initialCondition.getPeopleAbility().get(people);
        for (Integer key : abilityQueue) {
            peopleQueueOut(key, people);
        }
    }

    /**
     * 资源从相应队列出队
     *
     * @param key    队列种类
     * @param people 出队的资源
     */
    private void peopleQueueOut(int key, Integer people) {
        initialCondition.getPeopleQueue().get(key).remove(people);
    }

    private Integer peopleCapacityDecrement(int people) {
        int capacity = initialCondition.getPeopleCapacity().get(people) - 1;
        initialCondition.getPeopleCapacity().put(people, capacity);
        return capacity;
    }
}
